package com.googlecode.lightest.core

import org.codehaus.groovy.runtime.StackTraceUtils

/**
 * Represents a configuration of the test run
 */
class Configuration implements IConfiguration {
    static final UNSPECIFIED_PREFS = [:] as IPreferences
    static final UNSPECIFIED_ENV = [ id: 'unspecified' ] as ITestEnvironment
    static final DEFAULT_REPORTER =
        'com.googlecode.lightest.core.LightestReporter'
    static final DEFAULT_STRATEGY =
        'com.googlecode.lightest.core.SimpleDispatcherAssignmentStrategy'
    
    def classLoader
    def classPaths
    def outputDir
    def prefs
    def envs
    def reporter
    def strategy
    
    Configuration() {
        classLoader = new GroovyClassLoader(this.class.classLoader)
    }
    
    /**
     * Initializes the configuration.
     *
     * @param configText
     */
    void init(String configText) {
        def config
        
        if (configText) {
            def header = 'def config = new NodeBuilder()\n'
            
            try {
                config = Eval.me(header + configText)
                validate(config)
            }
            catch (e) {
                println 'Caught exception parsing the following configuration:'
                println configText
                StackTraceUtils.sanitize(e)
                throw e
            }
        }
        else {
            config = new Node(null, 'config')
        }
        
        setClassPaths(config)
        setOutputDir(config)
        setPreferences(config)
        setEnvironments(config)
        setReporter(config)
        setDispatcherAssignmentStrategy(config)
    }
    
    /**
     * Determines if the specified configuration is a valid one. If not, throws
     * an exception.
     *
     * @param config  the configuration to validate
     */
    private void validate(config) {
        // TODO - implement
    }
    
    private void setClassPaths(Node config) {
        classPaths = [ '.' ]
        
        if (config.classPaths.size() > 0) {
            config.classPaths[0].path.each {
                classPaths << it.value()
            }
        }
    }
    
    private void setOutputDir(Node config) {
        outputDir = config.outputDir[0]?.value() ?: 'lightest-report'
        outputDir = new File(outputDir).getCanonicalPath()
    }
    
    /**
     * Initializes the preferences instance according to the configuration. If
     * the configuration references a property that is not a property of the
     * preference, an exception is thrown.
     *
     * @param config  the configuration containing the preference information.
     *                It must have a "prefs" child with a "class" attribute.
     */
    private void setPreferences(Node config) {
        if (config.prefs.size() == 0) {
            prefs = UNSPECIFIED_PREFS
            return
        }
        
        assert config.prefs[0].'@class' != null
        
        prefs = loadClass(config.prefs.'@class').newInstance()
        
        config.prefs[0].children().each {
            prefs."${it.name()}" = it.value()
        }
    }
    
    private void setEnvironments(config) {
        if (config.envs.size() == 0 || config.envs.env.size() == 0) {
            envs = [ UNSPECIFIED_ENV ]
            return
        }
        
        assert config.envs[0].'@class' != null
        
        envs = []
        
        def clazz = loadClass(config.envs.'@class')
        
        for (envConfig in config.envs[0].env) {
            def env = clazz.newInstance()
            
            envConfig.children().each {
                if (it.name() == 'id') {
                    env.setId(it.value())
                }
                else {
                    env."${it.name()}" = it.value()
                }
            }
            
            envs << env
        }
    }
    
    private void setReporter(config) {
        def reporterClass = config.reporter.'@class'
        reporter = loadClass(reporterClass ?: DEFAULT_REPORTER).newInstance()
    }
    
    private void setDispatcherAssignmentStrategy(config) {
        def strategyClass = config.dispatcherAssignmentStrategy.'@class'
        strategy = loadClass(strategyClass ?: DEFAULT_STRATEGY).newInstance()
    }
    
    /**
     * Loads the class using a GroovyClassLoader whose classpath is set to
     * include all paths that have been parsed from the configuration text.
     * 
     * @param className
     */
    private Class loadClass(String className) {
        classPaths.each { path ->
            def url = new File(path).getCanonicalFile().toURL()
            if (! classLoader.getURLs().find { it == url }) {
                classLoader.addURL(url)
            }
        }

        return classLoader.loadClass(className, true)
    }

    /**
     * Returns the class loader used to load the configuration-related classes.
     */
    GroovyClassLoader getClassLoader() {
        return classLoader
    }
    
    /**
     * Returns a List of Strings representing class paths under which tasks
     * and potentially environment and preference classes are defined. The
     * current directory is added by default.
     */
    def getClassPaths() {
        return classPaths
    }

    /**
     * Returns the output directory for the test run report. The default output
     * directory is "lightest-report".
     */
    def getOutputDir() {
         return outputDir
     }
    
    /**
     * Returns an instance of the concrete implementation of IPreferences.
     */
    def getPreferences() {
        return prefs
    }
    
    /**
     * Returns a List of ITestEnvironment instances. A list containing the
     * single entry UNSPECIFIED_ENV will be returned if unspecified.
     */
    def getEnvironments() {
        return envs
    }
    
    /**
     * Returns an instance of the concrete subclass of LightestTestListener.
     * A DEFAULT_REPORTER will be returned if unspecified.
     */
    LightestTestListener getReporter() {
        return reporter
    }
    
    /**
     * Returns the specified dispatcher assignment strategy. A DEFAULT_STRATEGY
     * will be returned if unspecified.
     */
    IDispatcherAssignmentStrategy getDispatcherAssignmentStrategy() {
        return strategy
    }
}
